/********************************************************************
	file base:	EmbedAndExtract
	file ext:	h
	author:		JiangHuai
	
	purpose:	define function of core embed and extract function
*********************************************************************/

#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#ifndef EMBED_AND_EXTRACT
#define EMBED_AND_EXTRACT
#include "EmbedAndExtract.h"
#include <string.h>
#include <algorithm>
#include "Lib/CXImage600/include/ximage.h"
#include "Lib/CXImage600/include/zlib.h"
#include "Lib/crypto++/include/cryptlib.h"
#include "Lib/crypto++/include/des.h"
#include "Lib/crypto++/include/md5.h"
#include "Lib/crypto++/include/ripemd.h"
#include "Lib/crypto++/include/rng.h"
#include "Lib/crypto++/include/default.h"
#include "Lib/crypto++/include/randpool.h"
#include "Lib/crypto++/include/ida.h"
#include "Lib/crypto++/include/base64.h"
#include "Lib/crypto++/include/socketft.h"
#include "Lib/crypto++/include/wait.h"
#include "Lib/crypto++/include/factory.h"
#include "Lib/crypto++/include/whrlpool.h"
#include "Lib/crypto++/include/tiger.h"

USING_NAMESPACE(CryptoPP);

void Encompress(payload& toBeEncompressed);
void Decompress(payload& toBeDecompressed);

int LSBEmbed(Info& infoV);
int LSBExtract(Info& infoV);

void DESEncipher(payload& toBeEnciphered, const char* passPhrase);
void DESDecipher(payload& toBeDeciphered, const char* passPhrase);


int Embed() {
	int status = 0;

	const char* data = info.plainText.c_str();
	info.PayLoad.originalTextLength = strlen(data);
	info.PayLoad.data = (Byte*)malloc(info.PayLoad.originalTextLength);
	strcpy((char*)info.PayLoad.data, data);
	if (info.PayLoad.compressed) {
		Encompress(info.PayLoad);
	} else info.PayLoad.encompressedTextLength = info.PayLoad.originalTextLength;
	if (info.PayLoad.encrypted)
	{
		DESEncipher(info.PayLoad, info.key.c_str());
	} else info.PayLoad.embedLength = info.PayLoad.encompressedTextLength;
	switch (info.fileType)
	{
	case CXIMAGE_FORMAT_JPG:
	case CXIMAGE_FORMAT_GIF:
	case CXIMAGE_FORMAT_TIF:
	case CXIMAGE_FORMAT_BMP:
		status = LSBEmbed(info);
		break;
	}
	free(info.PayLoad.data);
	info.PayLoad.data = NULL;
	return status;
}

void Extract() {
	switch (info.fileType)
	{
	case CXIMAGE_FORMAT_JPG:
	case CXIMAGE_FORMAT_GIF:
	case CXIMAGE_FORMAT_TIF:
	case CXIMAGE_FORMAT_BMP:
		LSBExtract(info);
		break;
	}
	if (info.PayLoad.encrypted)
	{
		DESDecipher(info.PayLoad, info.key.c_str());
	}
	if (info.PayLoad.compressed) {
		Decompress(info.PayLoad);
	}

	info.plainText = string((char*)info.PayLoad.data);
	return;
}

void Encompress(payload& toBeEncompressed) {
	toBeEncompressed.encompressedTextLength = compressBound(toBeEncompressed.originalTextLength);
	Byte* encompressed = (Byte*)malloc(toBeEncompressed.encompressedTextLength);
	memset((void*)encompressed, 0, toBeEncompressed.encompressedTextLength);

	compress(encompressed, &toBeEncompressed.encompressedTextLength, toBeEncompressed.data, toBeEncompressed.originalTextLength);

	Byte* temp = (Byte*)malloc(toBeEncompressed.encompressedTextLength);
	memcpy((void*)temp, (void*)encompressed,toBeEncompressed.encompressedTextLength);
	std::swap(temp, encompressed);
	free(temp);

	std::swap(encompressed, toBeEncompressed.data);
	free(encompressed);
	return;
}

void Decompress(payload& toBeDecompressed) {
	Byte* decompressed = (Byte*) malloc(toBeDecompressed.originalTextLength);
	memset((void*)decompressed, 0, toBeDecompressed.encompressedTextLength);
	
	uncompress(decompressed, &toBeDecompressed.originalTextLength, toBeDecompressed.data, toBeDecompressed.encompressedTextLength);

	std::swap(decompressed, toBeDecompressed.data);
	free(decompressed);
	return;
}

void DESEncipher(payload& Payload, const char* passPhrase)
{
	//ByteQueue outdata;
	string outstr;

	DefaultEncryptor encryptor(passPhrase, new Base64Encoder(new StringSink(outstr)));
	encryptor.Put(Payload.data, Payload.encompressedTextLength);
	encryptor.MessageEnd();

	Payload.embedLength = strlen(outstr.c_str());
	Byte* newData = (Byte*)malloc(Payload.embedLength);
	strcpy((char*)newData, outstr.c_str());

	std::swap(newData, Payload.data);
	free(newData);
	return;
}

void DESDecipher(payload& Payload, const char* passPhrase)
{
	string outstr;
	
	Base64Decoder decryptor(new DefaultDecryptor(passPhrase, new StringSink(outstr)));
	int length = strlen((char*)Payload.data);
	decryptor.Put(Payload.data, Payload.embedLength);
	decryptor.MessageEnd();
	
	Byte* newData = (Byte*)malloc(Payload.encompressedTextLength);
	memcpy(newData, outstr.c_str(), Payload.encompressedTextLength);
	
	std::swap(newData, Payload.data);
	free(newData);
	return;
}

inline unsigned long getI(unsigned long length, int offset) {
	return ((unsigned long)offset) / length;
};
inline unsigned long getJ(unsigned long length, int offset) {
	return ((unsigned long)offset) % length;
};
inline uLong getM(uLong width, uLong height) {
	return width * height;
};

int LSBEmbedIn(Byte* data, int dataLength, CxImage& image, int& offset) {
	for (int dataIndex = 0; dataIndex < dataLength;++dataIndex) {
		int bitPos = 7;
		for(; bitPos >= 0; --bitPos) {
			Byte dataToEmbed = data[dataIndex];
			dataToEmbed &= 1 << bitPos;
			dataToEmbed = dataToEmbed >> bitPos;

			int i = getI(image.GetHeight(),offset);
			int j = getJ(image.GetHeight(),offset);
			//embed
			RGBQUAD color = image.GetPixelColor(i, j,false);
			color.rgbRed &= 254;
			color.rgbRed |= dataToEmbed;
			image.SetPixelColor(i, j ,color);
			++offset;

			if (offset > getM(image.GetHeight(), image.GetWidth())) {
				return 1;
			}
		}
	}
	return 0;
}

int LSBEmbed(Info& infoV) {
	infoV.embed.Copy(infoV.image);
	//embed payload
	int offset = 0;
	int err = LSBEmbedIn((Byte*)&infoV.PayLoad, sizeof(payload), infoV.embed, offset);
	if (err != 0) return err;
	//embed data
	err = LSBEmbedIn(infoV.PayLoad.data, infoV.PayLoad.embedLength, infoV.embed, offset);
	if (err != 0) return err;
	return 0;
}

int LSBExtractOut(Byte* data, int dataLength, CxImage& image, int& offset) {
	for (int dataIndex = 0; dataIndex < dataLength; ++dataIndex) {
		int bitPos = 7;
		for (; bitPos >= 0; --bitPos) {
			int i = getI(image.GetHeight(),offset);
			int j = getJ(image.GetHeight(),offset);
			RGBQUAD color = image.GetPixelColor(i, j, false);
			++offset;

			Byte bitData = color.rgbRed & 1;
			bitData = bitData << bitPos;

			data[dataIndex] |= bitData;
			if (offset > getM(image.GetHeight(), image.GetWidth())) {
				return 1;
			}
		}
	}
	return 0;
}

int LSBExtract(Info& infoV) {
	//get pay load
	int offset = 0;
	payload* payLoadData = (payload*)malloc(sizeof(payload));
	memset((void*)payLoadData, 0, sizeof(payload));
	int err = LSBExtractOut((Byte*)payLoadData, sizeof(payload), infoV.embed, offset);
	if (err != 0) return err;
	//get data
	Byte* data = (Byte*)malloc(payLoadData->embedLength+1);
	memset((void*)data, 0, payLoadData->embedLength+1);
	err = LSBExtractOut(data, payLoadData->embedLength, infoV.embed, offset);
	if (err != 0) return err;
	payLoadData->data = data;
	infoV.PayLoad = *payLoadData;

	free(payLoadData);
	return 0;
}
#endif
